package com.ljf.framework.job;

import com.ljf.framework.log.LjfLogManager;
import com.ljf.framework.log.LjfLogService;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.*;

/**
 * 说明：
 *     使用方法： 继承该类
 *     1、初始化任务五bean initJobHandlerMethodRepository()
 *     2、实现LjfJobService 重写 getTriggerParamList 获取需要执行的任务列表
 *     3、执行start() 定时执行任务
 *
 *     LjfJobThread 执行的任务线程
 *     LjfJobHandler 执行的任务处理器
 *
 * @Auther: lijinfeng
 * @Date: 2025/1/9
 */
public class LjfJobExecutor {

    private static final LjfLogService log = LjfLogManager.getLogger(LjfJobExecutor.class);

    public void start() throws Exception {

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

        List<TriggerParam> triggerParamList = LjfJobManager.getTriggerParamList();
        for (TriggerParam triggerParam: triggerParamList){
            LjfJobHandler ljfJobHandler = loadJobHandler(triggerParam.getHandlerName());
            LjfJobThread ljfJobThread = registLjfJobThread(triggerParam.getJobId(), ljfJobHandler, "");
            /*
                参数对应含义：
                command为被执行的线程；
                initialDelay为初始化后延时执行时间；
                period为两次开始执行最小间隔时间；
                unit为计时单位。

                scheduleAtFixedRate是以period为间隔来执行任务的，
                如果任务执行时间小于period，则上次任务执行完成后会间隔period后再去执行下一次任务
                ；但如果任务执行时间大于period，则上次任务执行完毕后会不间隔的立即开始下次任务。

                scheduleWithFixedDelay是不管任务执行多久，都会等上一次任务执行完毕后再延迟delay后去执行下次任务。
             */
            executor.scheduleAtFixedRate(
                    ljfJobThread,
                    0,
                    1000,
                    TimeUnit.MILLISECONDS);
        }

    }

    // ---------------------- job bean repository ----------------------
    public static void initJobHandlerMethodRepository(List<Object> ljfJobBeanList) {
        if (ljfJobBeanList==null || ljfJobBeanList.isEmpty()) {
            return;
        }
        // init job handler from method
        for (Object bean: ljfJobBeanList) {
            // method
            Method[] methods = bean.getClass().getDeclaredMethods();
            for (Method executeMethod : methods) {
                LjfJob ljfJob = executeMethod.getAnnotation(LjfJob.class);
                // registry
                registJobHandler(ljfJob, bean, executeMethod);
            }

        }

    }

    // ---------------------- job thread repository ----------------------
    private static ConcurrentMap<Integer, LjfJobThread> jobThreadRepository = new ConcurrentHashMap<Integer, LjfJobThread>();
    public static LjfJobThread registLjfJobThread(int jobId, LjfJobHandler handler, String removeOldReason){

        LjfJobThread newLjfJobThread = new LjfJobThread(jobId, handler);
//        newLjfJobThread.start();
        log.info(">>>>>>>>>>> register LjfJobThread success, jobId:{"+jobId+"}, handler:{"+ handler.toString()+"}" );

        LjfJobThread oldLjfJobThread = jobThreadRepository.put(jobId, newLjfJobThread);	// putIfAbsent | oh my god, map's put method return the old value!!!
        if (oldLjfJobThread != null) {
            oldLjfJobThread.toStop(removeOldReason);
            oldLjfJobThread.interrupt();
        }

        return newLjfJobThread;
    }

    public static LjfJobThread removeLjfJobThread(int jobId, String removeOldReason){
        LjfJobThread oldLjfJobThread = jobThreadRepository.remove(jobId);
        if (oldLjfJobThread != null) {
            oldLjfJobThread.toStop(removeOldReason);
            oldLjfJobThread.interrupt();

            return oldLjfJobThread;
        }
        return null;
    }

    public static LjfJobThread loadLjfJobThread(int jobId){
        return jobThreadRepository.get(jobId);
    }

    // ---------------------- job handler repository ----------------------
    private static ConcurrentMap<String, LjfJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, LjfJobHandler>();
    public static LjfJobHandler loadJobHandler(String name){
        return jobHandlerRepository.get(name);
    }

    protected static void registJobHandler(LjfJob ljfJob, Object bean, Method executeMethod){
        if (ljfJob == null) {
            return;
        }
        String name = ljfJob.value();
        //make and simplify the variables since they'll be called several times later
        Class<?> clazz = bean.getClass();
        String methodName = executeMethod.getName();
        if (name.trim().isEmpty()) {
            throw new RuntimeException("ljf-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
        }
        if (loadJobHandler(name) != null) {
            throw new RuntimeException("ljf-job jobhandler[" + name + "] naming conflicts.");
        }

        executeMethod.setAccessible(true);

        // init and destroy
        Method initMethod = null;
        Method destroyMethod = null;

        if (!ljfJob.init().trim().isEmpty()) {
            try {
                initMethod = clazz.getDeclaredMethod(ljfJob.init());
                initMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("ljf-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }
        if (!ljfJob.destroy().trim().isEmpty()) {
            try {
                destroyMethod = clazz.getDeclaredMethod(ljfJob.destroy());
                destroyMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("ljf-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }

        // registry jobhandler
        registJobHandler(name, new LjfJobMethodHandler(bean, executeMethod, initMethod, destroyMethod));

    }

    public static LjfJobHandler registJobHandler(String name, LjfJobHandler jobHandler){
        log.info(">>>>>>>>>>> ljf-job register jobhandler success, name:{"+name+"}, jobHandler:{"+jobHandler.toString()+"}");
        return jobHandlerRepository.put(name, jobHandler);
    }
}
